package boardgamer.yavalath.client.game;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import boardgamer.common.CyclicIterator;
import boardgamer.common.GameUpdate;
import boardgamer.common.client.AbstractActivity;
import boardgamer.common.client.Logger;
import boardgamer.common.net.PlayerId;
import boardgamer.yavalath.R;
import boardgamer.yavalath.client.SharedObjects;
import boardgamer.yavalath.client.Version;
import boardgamer.yavalath.client.YavalathActivity;
import boardgamer.yavalath.client.YavalathPlayer;
import boardgamer.yavalath.client.YavalathReplaySaver;
import boardgamer.yavalath.client.resources.CommonDialogHandler;
import boardgamer.yavalath.client.resources.StringHandler;
import boardgamer.yavalath.common.YavalathException;
import boardgamer.yavalath.common.YavalathGameServer;
import boardgamer.yavalath.common.YavalathGameState;
import boardgamer.yavalath.common.YavalathGameStateChange;
import boardgamer.yavalath.common.YavalathPlayerState;
import boardgamer.yavalath.common.YavalathTile;
import boardgamer.yavalath.server.YavalathGameServerImpl;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import javax.annotation.Nullable;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;

import static android.view.Menu.NONE;
import static boardgamer.common.client.Logger.log;
import static boardgamer.yavalath.client.YavalathReplaySaver.SaveCompleteHandler;
import static boardgamer.yavalath.common.YavalathPlayerState.PlayerMode.AI;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * @author garysoed
 */
public class GameActivity extends AbstractActivity<GameView> {

  public static final String ID_PLAYERS = "id_players";

  private static enum DialogId {
    GAMEOVER,
    RESTART,
    QUIT,
    FEEDBACK,
    SHARE,
    ABOUT;
    
    public static DialogId valueOf(final int id) {
      return Iterables.find(EnumSet.allOf(DialogId.class), new Predicate<DialogId>() {
        @Override
        public boolean apply(DialogId input) {
          return input.ordinal() == id;
        }
      });
    }
  }
  
  private YavalathGameServer server;
  private Map<PlayerId, YavalathPlayerState> playerStates;
  private int lastUpdateTimestamp = 0;
  private YavalathGameState state;
  @Nullable private YavalathDrawableTile preselectTile;
  @Nullable private Map<YavalathPlayer, YavalathPlayerState> players;

  private String gameOverMsg;

  public GameActivity() {
    super(R.layout.game, R.id.viewContainer);
  }

  // TODO: Class that handles this, built into server's client
  private synchronized void updateGameState() {
    List<GameUpdate<YavalathGameState, YavalathGameStateChange>> updates
        = server.refresh(lastUpdateTimestamp);
    for (GameUpdate<YavalathGameState, YavalathGameStateChange> update : updates) {
      YavalathGameStateChange gameStateChange = update.getStateChange();

      if (gameStateChange != null) {
        switch (gameStateChange.getEvent()) {
          case ADD_TOKEN:
            YavalathGameStateChange.PlayerAddToken addTokenChange
                = (YavalathGameStateChange.PlayerAddToken) gameStateChange;
            view.addPlayerMove(
                addTokenChange.getPlayer(), addTokenChange.getX(), addTokenChange.getY());

            displayNextPlayer(update.getEndState());
            break;

          case JOIN_GAME:
            // Nothing happens, since joining game happens locally
            // TODO-P1(2011/08/06): Implement when moving to networked model
            break;

          case PLAYER_LOSES:
            handlePlayerLoss((YavalathGameStateChange.PlayerLoses) gameStateChange);
            break;

          case PLAYER_WINS:
            handlePlayerWins((YavalathGameStateChange.PlayerWins) gameStateChange);
            break;

          case START_GAME:
            handleStartGame(update);
            break;

          case DRAW:
            view.displayDraw();
            break;

          default:
            throw new UnsupportedOperationException("Unsupported Event: "
                + gameStateChange.getEvent());
        }

        state = update.getEndState();
        view.setBoard(state.getBoard());

        // Add suggestion tiles for 3 players mode
        if (state.getActivePlayers().size() == 3) {
          CyclicIterator<PlayerId> playerIds = CyclicIterator.copyOf(state.getActivePlayers());
          playerIds.next();

          PlayerId nextPlayer = playerIds.peek();
          view.addSuggestionTiles(
              state.getNextWinningMoves(nextPlayer),
              state.getPlayerStates().get(nextPlayer).getColor());
        } else {
          view.clearSuggestionTiles();
        }
      }
    }

    lastUpdateTimestamp += updates.size();
  }

  private void handlePlayerLoss(YavalathGameStateChange.PlayerLoses gameStateChange) {
    final PlayerId loser = gameStateChange.getPlayer();
    final ImmutableSet<Pair<Integer, Integer>> losingTiles
        = ImmutableSet.copyOf(Iterables.transform(gameStateChange.getLosingTiles(),
        new Function<YavalathTile, Pair<Integer, Integer>>() {
          @Override
          public Pair<Integer, Integer> apply(YavalathTile input) {
            return Pair.create(input.getX(), input.getY());
          }
        }));
    view.displayPlayerLoss(losingTiles, loser, playerStates.get(loser).getColor());
  }

  private void handlePlayerWins(YavalathGameStateChange.PlayerWins winChange) {
    final PlayerId winner = winChange.getPlayer();
    view.displayPlayerWin(winner, playerStates.get(winner).getColor());
  }

  private void displayNextPlayer(YavalathGameState newState) {
    PlayerId nextPlayer = newState.getCurrentPlayer();
    view.displayNextPlayer(
        nextPlayer.getName(),
        playerStates.get(nextPlayer).getColor());
  }

  private void handleStartGame(final GameUpdate<YavalathGameState, YavalathGameStateChange> update) {
    playerStates = update.getEndState().getPlayerStates();
    PlayerId firstPlayer = update.getEndState().getCurrentPlayer();
    view.displayStartGame(
        playerStates,
        update.getEndState().getBoard(),
        firstPlayer.getName(),
        playerStates.get(firstPlayer).getColor());
  }

  @Override
  protected GameView createView() {
    return new GameView(this);
  }

  @Override
  protected void handleCreate(Bundle savedInstanceState) {
    startNewGame(((GamePlayersParcel) getIntent().getParcelableExtra(ID_PLAYERS)).getPlayers());
  }

  @Override
  protected void handleSaveInstanceState(Bundle outState) {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  @Override
  protected void handleStart() {
    updateGameState();
  }

  @Override
  protected void handleReady() {
    server.startGame();
  }

  @Override
  protected void handleStop() {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  @Override
  protected void handleResume() {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  @Override
  protected void handleRestart() {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  @Override
  protected void handlePause() {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  @Override
  protected void handleDestroy() {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  @Override
  protected void handleUpdate(int elapsedTime) {
    // TODO: This should be far less aggressive
    updateGameState();
  }

  void showGameOver(final String msg) {
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        gameOverMsg = msg;
        // TODO: Move to dialog manager
        showDialog(DialogId.GAMEOVER.ordinal());
      }
    });
  }

  void onTileSelected(@Nullable YavalathDrawableTile selectedTile) {
    if (selectedTile != null) {
      try {
        if (preselectTile == null || !preselectTile.equals(selectedTile)) {
          log(Log.INFO, "Selecting tile " + selectedTile);
          preselectTile = selectedTile;
          view.selectTile(selectedTile, state.getCurrentPlayer());
        } else {
          preselectTile = null;
          view.clearSelection();

          try {
            server.addToken(state.getCurrentPlayer(), selectedTile.getX(), selectedTile.getY());
          } catch (YavalathException exception) {
            handleException(exception);
          }
        }
      } catch (Exception ignored) {
        log(Log.ERROR, ignored.toString());
      }
    }
  }

  boolean canSelectTile() {
    return state != null
        && playerStates.get(state.getCurrentPlayer()).getMode() != AI
        && state.getPhase() == YavalathGameState.Phase.GAME;
  }

  @Override
  protected Dialog onCreateDialog(int id) {
    DialogId dialogId = DialogId.valueOf(id);
    AlertDialog.Builder builder = new AlertDialog.Builder(this);

    switch (dialogId) {
      case GAMEOVER:
        if (Version.SAVE_REPLAY.isEnabled()) {
          return new SaveReplayDialog(this, gameOverMsg);
        } else {
          builder.setMessage(gameOverMsg)
              .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id) {
                  restartGame();
                  dialog.dismiss();
                }
              })
              .setNegativeButton("No", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id) {
                  dialog.cancel();
                  quitGame();
                  dialog.dismiss();
                }
              });
          return builder.create();
        }
      case RESTART:
        builder.setMessage("Are you sure you want to restart?")
            .setCancelable(false)
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int id) {
                restartGame();
                dialog.dismiss();
              }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
                dialog.dismiss();
              }
            });
        return builder.create();
      case QUIT:
        builder.setMessage("Are you sure you want to quit?")
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int id) {
                quitGame();
                dialog.dismiss();
              }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
                dialog.dismiss();
              }
            });
        return builder.create();
      case ABOUT:
        return CommonDialogHandler.instance().createAboutDialog(this);
    }
    return super.onCreateDialog(id);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    // TODO: Move strings to strings.xml
    menu.add(NONE, DialogId.RESTART.ordinal(), NONE, "Restart")
        .setIcon(android.R.drawable.ic_menu_revert);
    menu.add(NONE, DialogId.QUIT.ordinal(), NONE, "Quit")
        .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
    menu.add(NONE, DialogId.FEEDBACK.ordinal(), NONE, "Feedback")
        .setIcon(android.R.drawable.ic_menu_send);
    menu.add(NONE, DialogId.SHARE.ordinal(), NONE, "Share")
        .setIcon(android.R.drawable.ic_menu_share);
    menu.add(NONE, DialogId.ABOUT.ordinal(), NONE, "About")
        .setIcon(android.R.drawable.ic_menu_info_details);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (DialogId.valueOf(item.getItemId())) {
      case SHARE:
        CommonDialogHandler.instance().showShareDialog(this);
        return true;
      case FEEDBACK:
        CommonDialogHandler.instance().showFeedbackDialog(this);
        return true;
      case ABOUT:
      case RESTART:
      case QUIT:
      case GAMEOVER:
        // Handled by onCreateDialog
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  private void handleException(YavalathException exception) {
    switch (exception.getErrorCode()) {
      case INVALID_MOVE:
        CommonDialogHandler.instance().showError(this, StringHandler.instance().errorInvalidMove);
        break;

      case HAS_FORCED_MOVE:
        CommonDialogHandler.instance().showError(this, StringHandler.instance().errorForcedMove);
        break;
    }
  }

  private void startNewGame(Map<YavalathPlayer, YavalathPlayerState> players) {
    this.players = checkNotNull(players, "players");

    // Create the server, and add initial players
    server = new YavalathGameServerImpl(SharedObjects.instance().getProFeatures().getGameSetting());
    for (Map.Entry<YavalathPlayer, YavalathPlayerState> pair : players.entrySet()) {
      server.joinGame(pair.getKey(), pair.getValue());
    }
    lastUpdateTimestamp = 0;
  }

  public void quitGame() {
    Intent intent = new Intent(getBaseContext(), YavalathActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(intent);
  }

  public void restartGame() {
    startNewGame(players);
  }

  public void saveReplay(String replayName, SaveCompleteHandler completeHandler) {
    // TODO: Destroy the saver when activity is destroyed
    YavalathReplaySaver replaySaver = new YavalathReplaySaver(server);
    replaySaver.save(replayName, completeHandler);
  }
}
